﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Drawing;
using System.IO;
using System.Security.Cryptography;

namespace ABII.AdminPanel
{
    public class AuthenticationException : Exception
    {
        public AuthenticationException(String message) : base(message)
        {
        }
    }
    public class AuthRequiredException : AuthenticationException
    {
        public AuthRequiredException(String message) : base(message)
        {
        }
    }
    public class Record
    {
        public Int32 ID { get; private set; }
        public String Name { get; private set; }

        public Record(Int32 id, String name)
        {
            ID = id;
            Name = name;
        }
    }
    public enum PERMISSION
    {
        NONE, 
        LOOK, 
        MODIFY
    }
    public class Avatar
    {
        private Image image;
        private String hash;
        public Avatar() {
            image = null;
            hash = "";
        }
        public Avatar(String file) {
            Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read);
            image = CreateImage(stream, 96, 96);
            hash = GetHash(image);
            stream.Close();
        }
        public Avatar(Stream stream) {
            image = CreateImage(stream, 96, 96);
            hash = GetHash(image);
        }
        public Avatar(Image image) {
            Stream stream = new MemoryStream();
            image.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
            this.image = CreateImage(stream, 96, 96);
            hash = GetHash(this.image);
            stream.Close();
        }
        private Image CreateImage(Stream stream, Int32 w, Int32 h) {
            Image image = Image.FromStream(stream);
            stream.Close();
            if (image.Width == 96 && image.Height == 96) return image;
            Bitmap bitmap = new Bitmap(w, h);
            bitmap.SetResolution(72, 72);
            Graphics graphic = Graphics.FromImage(bitmap);
            graphic.DrawImage(image, new Rectangle(0, 0, w, h), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);
            stream = new MemoryStream();
            bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
            bitmap.Dispose();
            graphic.Dispose();
            image.Dispose();
            return Image.FromStream(stream);
        }
        public String GetHash(Image image) {
            MD5 md5 = new MD5CryptoServiceProvider();
            String hash = "";
            MemoryStream stream = new MemoryStream();
            image.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
            stream.WriteByte(0xAC);
            foreach (Byte segment in md5.ComputeHash(stream.ToArray())) {
                hash += segment.ToString("X2");
            }
            return hash;
        }
        public Image Image {
            get { return image; }
        }
        public String Hash {
            get { return hash; }
        }
        public Byte[] Data {
            get {
                Stream stream = new MemoryStream();
                image.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
                Byte[] data = new Byte[stream.Length];
                stream.Position = 0;
                while (stream.Position < stream.Length) {
                    data[stream.Position] = (Byte)stream.ReadByte();
                }
                return data;
            }
        }
    }
    public abstract class QueryData
    {
        public PERMISSION Permission { get; set; }

        public abstract void Clear();
    }
    public class PrivateData : QueryData
    {
        public Avatar Avatar { get; set; }
        public List<Record> Friends { get; set; }

        public PrivateData()
        {
            Clear();
        }

        public override void Clear()
        {
            Avatar = new Avatar();
            Friends = new List<Record>(); 
        }
    }
    public class SecurityData : QueryData
    {
        public String Username { get; set; }
        public String Password { get; set; }
        public String Rank { get; set; }
        public String Gender { get; set; }
        public DateTime Birthday { get; set; }
        public String Question { get; set; }
        public String Answer { get; set; }

        public SecurityData()
        {
            Clear();
        }

        public override void Clear()
        {
            Username = Password = Rank = Gender = Question = Answer = "";
            Birthday = DateTime.Now; 
        }
    }
    static public class Database
    {
        public static String Username = "";
        public static Int32 RankID = 0;
        public static List<Record> Ranks = new List<Record>();
        public static List<Record> Users = new List<Record>();
        public static List<Record> Genders = new List<Record>();
        public static List<Record> States = new List<Record>();
        public static List<Record> Relations = new List<Record>();

        public static void Export(String path)
        {
            Queries.ExportData(path);
        }

        public static void Authenticate(String username, String password)
        {
            if (!(IsValidCredential(username) && IsValidCredential(password)))
            {
                throw new AuthenticationException("Invalid username/password!" + Environment.NewLine + 
                                                  "Usernames and passwords contains only upper and lowercase letter, " + Environment.NewLine + 
                                                  "digits and underlines (maximum 16 characters long)!");
            }
            RankID = Queries.Authenticate(username, password);
            if (RankID == 0 || RankID > 3)
            {
                throw new AuthenticationException("Invalid username/password or the user does not have access rights to this client!");
            }
            Username = username;
        }

        public static Boolean CanLook(PERMISSION right)
        {
            return (right & PERMISSION.LOOK) == PERMISSION.LOOK;
        }

        public static Boolean CanModify(PERMISSION right)
        {
            return (right & PERMISSION.MODIFY) == PERMISSION.MODIFY;
        }

        public static void GetPublicData()
        {
            Validate();
            Ranks = Queries.GetRanks();
            Genders = Queries.GetGenders();
            Users = Queries.GetUsers();
            Relations = Queries.GetRelations();
        }

        public static void SaveSecurityData(String username, SecurityData data)
        {
            Validate();
            Int32 rankID = Ranks.Find(rank => rank.Name.Equals(data.Rank)).ID;
            Int32 genderID = Genders.Find(gender => gender.Name.Equals(data.Gender)).ID;
            Queries.SaveSecurityData(username, data, rankID, genderID);
        }

        public static SecurityData GetSecurityData(String username)
        {
            Validate();
            PERMISSION permission = GetRight(username, Users.Find(user => user.Name == username).ID);
            return Queries.GetSecurityData(username, permission);
        }

        public static PrivateData GetPrivateData(String username)
        {
            Validate();
            PERMISSION permission = GetRight(username, Users.Find(user => user.Name == username).ID);
            return Queries.GetPrivateData(username, permission);
        }

        public static Boolean IsValidRank(String username, String rank)
        {
            Int32 rankID = Ranks.Find(r => r.Name.Equals(rank)).ID;
            return GetRight(username, rankID) == PERMISSION.MODIFY;
        }

        private static PERMISSION GetRight(String username, Int32 rankID)
        {
            if (RankID < rankID || Username.Equals(username))
            {
                return PERMISSION.MODIFY;
            }
            else if (RankID == rankID)
            {
                return PERMISSION.LOOK;
            }
            else
            {
                return PERMISSION.NONE;
            }
        }

        public static Int32 GetUserRank(String username)
        {
            return Users.Find(user => user.Name.Equals(username)).ID;
        }

        public static String GetRelation(Int32 relationID)
        {
            return Relations.Find(relation => relation.ID.Equals(relationID)).Name;
        }

        public static void GetReport(Boolean rank, Boolean gender, Boolean state, Boolean ban, Boolean silence)
        {
            List<Report.Title> titles = new List<Report.Title>();
            Int32 count = Users.Count;
            if (rank) titles.Add(new Report.Title("Rank", Queries.GetReportByRank(count)));
            if (gender) titles.Add(new Report.Title("Gender", Queries.GetReportByGender(count)));
            if (state) titles.Add(new Report.Title("State", Queries.GetReportByState(count)));
            //if (ban) title.Add(new Report.Title("Ban", GetReportByBan(count)));
            //if (silence) title.Add(new Report.Title("Silence", GetReportBySilence(count)));

            //title.Add(new Report.Title("Users with the most active relations", GetReportByRelation(count)));
            //title.Add(new Report.Title("Blacklist", GetReportByBlacklist()));
            Report report = new Report(titles);
        }

        public static void Reset()
        {
            RankID = 0;
            Ranks.Clear();
            Users.Clear();
            Genders.Clear();
            States.Clear();
            Dispose();
        }

        public static Boolean CanSee(Int32 rankID)
        {
            return true;
        }

        private static void Validate()
        {
            if (RankID == 0 || RankID > 3)
            {
                throw new AuthRequiredException("Authentication required for this task!");
            }
        }

        private static Boolean IsValidCredential(String username)
        {
            return Regex.IsMatch(username, "^[a-zA-Z0-9_]{4,16}$");
        }

        public static void Dispose()
        {
            Queries.Dispose();
        }
    }
}
